require'zdc.idutil'
local isparent=zdc.idutil.IsParent
module(..., package.seeall)

VirDb = {}
VirDb.__index = VirDb

function _getdbwherefun(where)
	if not where or #where==0 then return end
	local f=assert(loadstring('return ' .. where))
	local env={}
	local _dbfun={
		id='id',
		pid='pid',
		ik='ik',
		ts='ts',
		AND=function(...)
			local cnt=select('#',...)
			for i=1,cnt do
				if not select(i,...) then return false end
			end
			return cnt>0
		end,
		OR=function(...)
			for i=1,select('#',...) do
				if select(i,...) then return true end
			end
			return false
		end,
		GT=function(k,a)
			return k>a
		end,
		LT=function(k,a)
			return k<a
		end,
		GE=function(k,a)
			return k>=a
		end,
		LE=function(k,a)
			return k<=a
		end,
		EQ=function(k,a)
			return k==a
		end,
	}
	local _wheremeta= {__index=function (t,k)
		local v=env.node[k]
		if v~=nil then return v end
		return _dbfun[k]
	end}
	setmetatable(env,_wheremeta)
	setfenv(f,env)
	return f,env
end

function new(params)
	local obj = {}
	setmetatable(obj, VirDb)

	-- Establish the table used for node storage
	obj.store = {}
	obj.cachesub={}
	return obj 
end

function VirDb:transdo(fn)
	return fn()
end

function VirDb:deflate_node(a,b,c)
	return zdc.deflate.deflate_node_cp(a,b,c)
end

function VirDb:node_exists(id)
	return self.store[id] ~= nil
end

function VirDb:get_node(id)
	return self.store[id]
end

function VirDb:save_node(id,data,ik,ts)
	local parent,rest=string.match(id, "^(.+)/(.-)$")
	parent=parent or ''
	local nd={id=id,pid=parent,data=data,ik=ik or 0,ts=ts or os.time()}
	self.store[id]=nd
	local par=self.cachesub[parent]
	if not par then
		par={[id]=nd}
		self.cachesub[parent]=par
	else
		par[id]=nd
	end
end

function VirDb:get_child_nodes(parentid)
	local nodes = {}
	local par=self.cachesub[parentid]
	if par then
		for id,v in pairs(par) do
			nodes[id]=v
		end
	end
	return nodes
end
function VirDb:get_child_nodes_ex(parentid,where,sort,limit)
	local nodes = {}
	local fw,fenv=_getdbwherefun(where)
	local par=self.cachesub[parentid]
	if par then
		if fw then
			for id,v in pairs(par) do
				fenv.node=v
				if fw() then
					nodes[id]=v
				end
			end
		else
			for id,v in pairs(par) do
				nodes[id]=v
			end
		end
	end
	local st={}
	for id,v in pairs(nodes) do
		table.insert(st,id)
	end
	if sort or limit then
		if sort then
			table.sort(st,function(a,b)
				for k,op in string.gmatch(sort,"(%a+)(%-?)") do
					local ika,ikb
					if k=='ik' then
						ika,ikb=nodes[a].ik,nodes[b].ik
					elseif k=='ts' then
						ika,ikb=nodes[a].ts,nodes[b].ts
					elseif k=='id' then
						ika,ikb=nodes[a].id,nodes[b].id
					else
						error('unknow sort:' .. sort)
					end
					if ika~=ikb then
						if op=='-' then
							return ika>ikb
						else
							return ika<ikb
						end
					end
				end
				return false
			end)
		end
		if limit then
			for i=limit+1,#st do
				nodes[st[i]]=nil
			end
		end
	end
	return nodes,st
end

function VirDb:get_nodes_by_prefix(prefix)
	local nodes = {}
	prefix = prefix or ""
	local preflen = #prefix
	for id,v in pairs(self.store) do
		if string.sub(id,1,preflen)==prefix then
			nodes[id]=v
		end
	end
	return nodes
end
function VirDb:get_nodes_by_prefix_ex(prefix,where,sort,limit)
	prefix = prefix or ""
	local nodes = {}
	local fw,fenv=_getdbwherefun(where)
	local prelen=#prefix
	for id,v in pairs(self.store) do
		if id:sub(1,prelen)==prefix then
			if fw then
				fenv._ik=v.ik
				fenv._ts=v.ts
				if fw() then
					nodes[id]=v
				end
			else
				nodes[id]=v
			end
		end
	end
	local st={}
	for id,v in pairs(nodes) do
		table.insert(st,id)
	end
	if sort or limit then
		if sort then
			table.sort(st,function(a,b)
				for k,op in string.gmatch(sort,"(%a+)(%-?)") do
					local ika,ikb
					if k=='ik' then
						ika,ikb=nodes[a].ik,nodes[b].ik
					elseif k=='ts' then
						ika,ikb=nodes[a].ts,nodes[b].ts
					elseif k=='id' then
						ika,ikb=nodes[a].id,nodes[b].id
					else
						error('unknow sort:' .. sort)
					end
					if ika~=ikb then
						if op=='-' then
							return ika>ikb
						else
							return ika<ikb
						end
					end
				end
				return false
			end)
		end
		if limit then
			for i=limit+1,#st do
				nodes[st[i]]=nil
			end
		end
	end
	return nodes,st
end
function VirDb:get_ids_by_prefix(prefix,some)
	local ids = {}
	local counter = 0
	prefix = prefix or ""
	local preflen = prefix:len()
	for id,_ in pairs(some or self.store) do
		if string.sub(id,1,preflen)==prefix then
			table.insert(ids,id)
		end
	end
	return ids
end

function VirDb:get_ids_by_prefix_ex(prefix,where,sort,limit)
	local nodes = {}
	local fw,fenv=_getdbwherefun(where)
	local prelen=#prefix
	for id,v in pairs(self.store) do
		if id:sub(1,prelen)==prefix then
			if fw then
				fenv._ik=v.ik
				fenv._ts=v.ts
				if fw() then
					nodes[id]=v
				end
			else
				nodes[id]=v
			end
		end
	end
	local st={}
	for id,v in pairs(nodes) do
		table.insert(st,id)
	end

	if sort or limit then
		if sort then
			table.sort(st,function(a,b)
				for k,op in string.gmatch(sort,"(%a+)(%-?)") do
					local ika,ikb
					if k=='ik' then
						ika,ikb=nodes[a].ik,nodes[b].ik
					elseif k=='ts' then
						ika,ikb=nodes[a].ts,nodes[b].ts
					elseif k=='id' then
						ika,ikb=nodes[a].id,nodes[b].id
					else
						error('unknow sort:' .. sort)
					end
					if ika~=ikb then
						if op=='-' then
							return ika>ikb
						else
							return ika<ikb
						end
					end
				end
				return false
			end)
		end
		if limit then
			for i=limit+1,#st do
				st[i]=nil
			end
		end
	end
	return st
end

function VirDb:get_child_ids(parentid)
	local nodes = {}
	local par=self.cachesub[parentid]
	if par then
		for id,v in pairs(par) do
			table.insert(nodes,id)
		end
	end
	return nodes
end
function VirDb:get_child_ids_ex(parentid,where,sort,limit)
	local nodes = {}
	local fw,fenv=_getdbwherefun(where)
	local par=self.cachesub[parentid]
	if par then
		if fw then
			for id,v in pairs(par) do
				fenv.node=v
				if fw() then
					nodes[id]=v
				end
			end
		else
			for id,v in pairs(par) do
				nodes[id]=v
			end
		end
	end
	local st={}
	for id,v in pairs(nodes) do
		table.insert(st,id)
	end
	if sort or limit then
		if sort then
			table.sort(st,function(a,b)
				for k,op in string.gmatch(sort,"(%a+)(%-?)") do
					local ika,ikb
					if k=='ik' then
						ika,ikb=nodes[a].ik,nodes[b].ik
					elseif k=='ts' then
						ika,ikb=nodes[a].ts,nodes[b].ts
					elseif k=='id' then
						ika,ikb=nodes[a].id,nodes[b].id
					else
						error('unknow sort:' .. sort)
					end
					if ika~=ikb then
						if op=='-' then
							return ika>ikb
						else
							return ika<ikb
						end
					end
				end
				return false
			end)
		end
		if limit then
			for i=limit+1,#st do
				st[i]=nil
			end
		end
	end
	return st
end
function VirDb:get_cnt_by_prefix(prefix,some)
	local cnt=0
	prefix = prefix or ""
	local preflen = prefix:len()
	for id,_ in pairs(some or self.store) do
		if string.sub(id,1,preflen)==prefix then
			cnt=cnt+1
		end
	end
	return cnt
end

function VirDb:get_cnt_by_prefix_ex(prefix,where,sort,limit)
	local cnt=0
	local fw,fenv=_getdbwherefun(where)
	local prelen=#prefix
	for id,v in pairs(self.store) do
		if id:sub(1,prelen)==prefix then
			if fw then
				fenv._ik=v.ik
				fenv._ts=v.ts
				if fw() then
					cnt=cnt+1
				end
			else
				cnt=cnt+1
			end
		end
	end
	return cnt
end

function VirDb:get_child_cnt(parentid)
	local cnt=0
	local par=self.cachesub[parentid]
	if par then
		for id,v in pairs(par) do
			cnt=cnt+1
		end
	end
	return cnt
end
function VirDb:get_child_cnt_ex(parentid,where)
	local cnt=0
	local fw,fenv=_getdbwherefun(where)
	local par=self.cachesub[parentid]
	if par then
		if fw then
			for id,v in pairs(par) do
				fenv.node=v
				if fw() then
					cnt=cnt+1
				end
			end
		else
			for id,v in pairs(par) do
				cnt=cnt+1
			end
		end
	end
	return cnt
end
function VirDb:del_node(id,deep)
	self.store[id]=nil
	if deep then
		self:del_sub_nodes(id)
	else
		self.cachesub[id]=nil
	end
	local parent,rest=string.match(id, "^(.+)/(.-)$")
	parent=parent or ''
	if self.cachesub[parent] then
		self.cachesub[parent][id]=nil
	end
end

function VirDb:del_sub_nodes(parentid)
	local store=self.store
	local cachesub=self.cachesub
	local sid=self:get_ids_by_prefix(parentid .. '/')
	for i=1,#sid do
		store[sid[i]]=nil
	end
	sid=self:get_ids_by_prefix(parentid .. '/',cachesub)
	for i=1,#sid do
		cachesub[sid[i]]=nil
	end
end

function VirDb:get_label(id)
	local row=self._labelrow
	if row then
		return row.idx,row.sum,row.timestamp,row.usr
	else
		return 0,'',''
	end
end

function VirDb:set_label(id, idx,sum,timestamp,usr)
	assert (id)
	assert(idx)
	assert(sum)
	--assert(timestamp)
	--assert(usr)
	self._labelrow={idx=idx,sum=sum,timestamp=timestamp,usr=usr}
end

function VirDb:get_nodes_sum(rootid, sumfun)
	return sumfun(nil,rootid,'')
end

function VirDb:__RestoreFromFile(path)
	self.cachesub={}
	self.store={}
	
	local f, err = io.open(path,'rb')
	if not f then return nil,err end
	local data = f:read("*all")
	f:close()
	local f,err=loadstring(data)
	if not f then return nil,err end
	self.store=f()
	
	local cachesub=self.cachesub
	for k,v in pairs(self.store) do
		v.id=k
		if not v.pid then
			local parent,rest=string.match(k, "^(.+)/(.-)$")
			v.pid=parent or ''
		end
		local parent,id=v.pid,k
		local par=cachesub[parent]
		if not par then
			par={[id]=v}
			cachesub[parent]=par
		else
			par[id]=v
		end
	end
	return true
end

function VirDb:__SaveToFile(path,opt)
	opt=opt or {}
	zdc.ds.util.suredir(path)
	--print('__SaveToFile','serialize','begin',os.clock())
	
	local str=zdc.doc.newStrStack()
	for k,v in pairs(self.store) do
		zdc.doc.addString(str,'[',zdc.doc.serialize(k),']={data=',zdc.doc.serialize(v.data),'};\n')
	end
	local data='{\n'..table.concat(str)..'}'
	--print('__SaveToFile','serialize','end',os.clock())

	local f, err = io.open(path, "wb")
	if not f then return nil,err end
	if opt.binmode then
		f:write(string.dump(loadstring('return '..data)))
	else
		f:write('return ',data)
	end
	f:close()
	return true
end

